﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Medianamik.Core.Action;
using Medianamik.Core.Security;

namespace Medianamik.Core
{
    /// <summary>
    ///   Facade permettant de gérer les Types Medianamik
    /// </summary>
    public static class NodeTypeManager
    {
        private static readonly object locker = new object();

        private static NodeTypeProvider _nodeTypeProvider;

        internal static NodeTypeProvider Provider
        {
            get { return _nodeTypeProvider ?? (_nodeTypeProvider = new NodeTypeProvider()); }
            set { _nodeTypeProvider = value; }
        }

        public static void Recycle()
        {
            Provider.Reload();
        }

        public static bool TypeHasChildren(string pTypeName)
        {
            var nodeType = GetNodeType(pTypeName);
            if (nodeType == null)
            {
                return false;
            }
            return TypeHasChildren(nodeType.ID);
        }

        public static bool TypeHasChildren(Guid pTypeID)
        {
            return Provider.AllTypes.Any(t => t.ParentID.HasValue && t.ParentID.Value.Equals(pTypeID));
        }

        public static int TypeNodesCount(string pTypeName)
        {
            var type = GetNodeType(pTypeName);

            if (type == null)
                return default(int);

            return TypeNodesCount(type.ID);
        }

        public static int TypeNodesCount(Guid pTypeID)
        {
            return TypeNodesCount(pTypeID, false);
        }

        public static int TypeNodesCount(Guid pTypeID, bool blockInheritance)
        {
            return DALProviderManager.DALProvider.NodeTypeDALProvider.TypeNodesCount(pTypeID, blockInheritance);
        }

        public static bool PropertyHasDuplicateValues(Guid pPropertyID)
        {
            return DALProviderManager.DALProvider.NodeTypeDALProvider.PropertyHasDuplicateValues(pPropertyID);
        }

        public static IEnumerable<NodeType> GetLegitimateChildTypes(Guid? pParentTypeID)
        {
            return (from t in Provider.AllTypes
                    where (from r in Provider.AllHierarchicalRules
                           where r.ParentItemID.Equals(pParentTypeID)
                           select r.ItemID).Contains(t.ID) && !t.IsAbstract && !t.IsManyToMany
                    select t).Union(Provider.UnrestrictedTypes);
        }

        public static IEnumerable<NodeType> GetLegitimateParentTypes(Guid pTypeID)
        {
            var fakeTypes = new List<NodeType> {new NodeType(Guid.Empty) {Name = "Racine", IsSystem = true}};

            if (Provider.UnrestrictedTypes.Any(t => t.ID.Equals(pTypeID)))
                return Provider.AllTypes;

            return (from t in Provider.AllTypes.Union(fakeTypes)
                    where (from r in Provider.AllHierarchicalRules
                           where r.ItemID.Equals(pTypeID)
                           select r.ParentItemID).Contains(t.ID)
                    select t);
        }

        public static bool IsUnrestricted(NodeType type)
        {
            return Provider.UnrestrictedTypes.Any(t => t.ID.Equals(type.ID));
        }

        public static IEnumerable<TypeTemplate> GetTemplates(Guid pTypeID)
        {
            return Provider.AllTemplates.Where(t => t.NodeTypeId.Equals(pTypeID));
        }

        //Propriété pouvant éventuellement être ajoutées à l'interface
        public static IEnumerable<IProperty> GetEligibleProperties(Guid interfaceId)
        {
            var implementers = GetNodeTypes(interfaceId);

            var count = implementers.Count();
            var eligibleProperties = new List<IProperty>();

            if (count == 0) return eligibleProperties;

            var interf = implementers.First().NodeTypeInterface;

            if (count == 1)
            {
                eligibleProperties.AddRange(implementers.ElementAt(0).Owner
                                                .GetAllProperties().Cast<IProperty>());
            }
            else
            {
                var properties = implementers.SelectMany(i => i.Owner.GetAllProperties()).Distinct();

                for (var i = 0; i < properties.Count(); i++)
                {
                    var eligibleProperty = properties.ElementAt(i);

                    if (
                        implementers.All(
                            t => t.Owner.GetAllProperties().Any(p => eligibleProperty.IsEquivalentToTypeProperty(p))))
                        eligibleProperties.Add(eligibleProperty);
                }
            }

            eligibleProperties.RemoveAll(prop => interf
                                                     .Properties.Any(p => p.IsEquivalentToTypeProperty(prop)));

            return eligibleProperties;
        }

        public static IEnumerable<NodeTypeInterfaceImplementation> GetNodeTypeInterfaces(Guid typeId)
        {
            return DALProviderManager.DALProvider.NodeTypeDALProvider
                .GetNodeTypeInterfaces(typeId);
        }

        public static IEnumerable<NodeTypeInterfaceImplementation> GetNodeTypes(Guid interfaceId)
        {
            return DALProviderManager.DALProvider.NodeTypeDALProvider
                .GetNodeTypes(interfaceId);
        }

        public static NodeType GetNodeTypeByNodeID(Guid pNodeID)
        {
            return NodeManager.GetNode(pNodeID).NodeType;
        }

        /// <summary>
        ///   Charge un Type Medianamik en fonction de son TypeID
        /// </summary>
        /// <param name = "pTypeID">Le TypeID (Guid)</param>
        /// <returns>Un object de type NodeType</returns>
        public static NodeType GetNodeType(Guid pTypeID)
        {
            return Provider.AllTypes.SingleOrDefault(t => t.ID.Equals(pTypeID));
        }

        public static NodeTypeInterface GetNodeTypeInterface(Guid interfaceId)
        {
            return Provider.AllInterfaces.SingleOrDefault(t => t.ID.Equals(interfaceId));
        }

        /// <summary>
        ///   Charge un Type Medianamik en fonction de son TypeName
        /// </summary>
        /// <param name = "pTypeID">Le TypeName</param>
        /// <returns>Un object de type NodeType</returns>
        public static NodeType GetNodeType(string pTypeName)
        {
            return
                Provider.AllTypes.SingleOrDefault(
                    t => t.Name.Equals(pTypeName, StringComparison.InvariantCultureIgnoreCase));
        }

        public static NodeTypeInterface GetNodeTypeInterface(string interfaceName)
        {
            return
                Provider.AllInterfaces.SingleOrDefault(
                    t => t.Name.Equals(interfaceName, StringComparison.InvariantCultureIgnoreCase));
        }

        /// <summary>
        ///   Charge une liste de Types Medianamik en fonction de leur ParentTypeID
        ///   Si le ParentTypeID est null, les Types racine seront chargés
        /// </summary>
        /// <param name = "pTypeID">Le TypeID du Parent (Guid)</param>
        /// <returns>Une list d'objets de type NodeType</returns>
        public static List<NodeType> GetChildTypes(Guid? pParentTypeID)
        {
            return Provider.AllTypes.Where(t => t.ParentID == pParentTypeID).ToList();
        }

        /// <summary>
        ///   Charge tous les Types héritant du ParentType
        /// </summary>
        /// <param name = "pParentId"></param>
        /// <returns></returns>
        public static List<NodeType> GetAllChildTypes(Guid pTypeId)
        {
            return GetChildTypesRecursive(GetNodeType(pTypeId)).ToList();
        }

        private static IEnumerable<NodeType> GetChildTypesRecursive(NodeType pNodeType)
        {
            var childTypes = GetChildTypes(pNodeType.ID);
            return childTypes.Concat(childTypes.SelectMany(t => GetChildTypesRecursive(t)));
        }

        /// <summary>
        ///   Charge une liste de Types Medianamik en fonction de leur ParentTypeName
        /// </summary>
        /// <param name = "pTypeID">Le TypeName du Parent</param>
        /// <returns>Une list d'objets de type NodeType</returns>
        public static List<NodeType> GetChildTypes(string pTypeName)
        {
            var parentType = GetNodeType(pTypeName);
            if (parentType == null)
            {
                return new List<NodeType>();
            }
            return GetChildTypes(parentType.ID);
        }

        public static List<NodeType> GetParentTypes(Guid pTypeID)
        {
            return GetParentTypes(pTypeID, false);
        }

        public static List<NodeType> GetParentTypes(Guid pTypeID, bool pInclusive)
        {
            var childType = GetNodeType(pTypeID);
            var resultCol = new List<NodeType>();
            if (childType == null)
            {
                return resultCol;
            }

            var parentTypes = (from id in childType.GetParentIds(pInclusive)
                               select GetNodeType(id)).Where(t => t != null);

            return parentTypes.ToList();
        }

        public static List<NodeType> GetParentTypes(string pTypeName)
        {
            return GetParentTypes(pTypeName, false);
        }

        public static List<NodeType> GetParentTypes(string pTypeName, bool pInclusive)
        {
            var type = GetNodeType(pTypeName);
            return type == null
                       ? new List<NodeType>()
                       : GetParentTypes(type.ID, pInclusive);
        }

        public static IEnumerable<NodeType> EligibleLookupTypes
        {
            get
            {
                return from type in Provider.AllTypes
                       where type.GetAllProperties()
                           .Any(p => p.DataType.ID == DataTypes.Varchar || p.DataType.ID == DataTypes.Int)
                       select type;
            }
        }

        public static List<NodeType> GetManyToManyTypes()
        {
            return Provider.AllTypes.Where(t => t.IsManyToMany).ToList();
        }

        public static List<NodeType> GetAllTypes(bool pIncludeManyToManys)
        {
            return pIncludeManyToManys
                       ? Provider.AllTypes
                       : Provider.AllTypes.Where(t => !t.IsManyToMany).ToList();
        }

        public static IEnumerable<NodeTypeInterface> GetAllNodeTypeInterfaces()
        {
            return Provider.AllInterfaces;
        }

        public static NodeTypeInterface SaveInterface(NodeTypeInterface pInterface)
        {
            return SaveInterfaces(new List<NodeTypeInterface> {pInterface}).FirstOrDefault();
        }

        public static NodeType SaveType(NodeType pType)
        {
            return SaveTypes(new List<NodeType> {pType}).FirstOrDefault();
        }

        public static IEnumerable<T> SaveInterfaces<T>(IEnumerable<T> interfaces)
            where T : NodeTypeInterface
        {
            var interfacesToSave = new List<T>(interfaces);

            lock (locker)
            {
                var nodeTypeInterfaceDataShells = new List<NodeTypeInterfaceDataShell>();

                foreach (var interf in interfacesToSave)
                {
                    if (interf.Status == EntityStates.Deleted)
                    {
                        nodeTypeInterfaceDataShells.Add(new NodeTypeInterfaceDataShell(interf));
                    }
                    else
                    {
                        var dirtyProperties = new List<NodeTypeInterfaceProperty>();
                        var newProperties = new List<NodeTypeInterfaceProperty>();
                        var deletedProperties = new List<NodeTypeInterfaceProperty>();

                        Guid typeId;
                        if (NodeTypeInterfaceExists(interf.Name, out typeId))
                        {
                            if (interf.Status == EntityStates.New)
                                throw new CoreException("Interface name must be unique.");
                            if (!interf.ID.Equals(typeId))
                                throw new CoreException("Cannot change interface name. Name already exists.");
                        }

                        interf.Properties.ForEach(p =>
                                                      {
                                                          if (p.Status == EntityStates.New)
                                                              newProperties.Add(p);
                                                          else
                                                              dirtyProperties.Add(p);
                                                      });

                        deletedProperties.AddRange(interf.Properties.Removed);

                        nodeTypeInterfaceDataShells.Add(new NodeTypeInterfaceDataShell(interf,
                                                                                       newProperties.Union(
                                                                                           dirtyProperties),
                                                                                       deletedProperties));
                    }
                }

                DALProviderManager.DALProvider.NodeTypeDALProvider.TransacSaveInterfaces(nodeTypeInterfaceDataShells);
                Provider.Reload();

                foreach (var nodeTypeInterfaceDataShell in nodeTypeInterfaceDataShells)
                {
                    if (nodeTypeInterfaceDataShell.NodeTypeInterface.Status == EntityStates.Deleted)
                    {
                        interfacesToSave.RemoveAt(interfacesToSave.FindIndex(t =>
                                                                             t.ID.Equals(
                                                                                 nodeTypeInterfaceDataShell.
                                                                                     NodeTypeInterface.ID)));
                    }
                    else
                    {
                        var index = interfacesToSave.FindIndex(t =>
                                                               t.ID.Equals(
                                                                   nodeTypeInterfaceDataShell.NodeTypeInterface.ID));

                        if (interfacesToSave[index].Status == EntityStates.New)
                            interfacesToSave[index].Status = EntityStates.Loaded;
                    }
                }
            }

            return interfacesToSave;
        }

        //TODO : DeletedBranch

        //Nouvelles méthode permettant de sauvegarder plusieurs types de manière transactionnelle
        public static IEnumerable<T> SaveTypes<T>(IEnumerable<T> types)
            where T : NodeType
        {
            //TODO: UpdatedProperties - Voir UpdateProperty

            var typesToSave = new List<T>(types);

            lock (locker)
            {
                var nodeTypeDataShells = new List<NodeTypeDataShell>();

                foreach (var type in typesToSave)
                {
                    if (type.Status == EntityStates.Deleted)
                    {
                        ActionManager.Instance.OnBeforeDeleteNodeType(new ActionEventArgs<NodeType>(type));
                        nodeTypeDataShells.Add(new NodeTypeDataShell(type));
                    }
                    else
                    {
                        var dirtyProperties = new List<NodeTypeProperty>();
                        var newProperties = new List<NodeTypeProperty>();
                        var deletedProperties = new List<NodeTypeProperty>();
                        var newHierarchicalTypeRules = new List<HierarchicalRule>();
                        var deletedHierarchicalTypeRules = new List<HierarchicalRule>();
                        var newHierarchicalNodeRules = new List<HierarchicalRule>();
                        var deletedHierarchicalNodeRules = new List<HierarchicalRule>();
                        var newTemplates = new List<TypeTemplate>();
                        var deletedTemplates = new List<TypeTemplate>();
                        var newInterfaces = new List<NodeTypeInterfaceImplementation>();
                        var deletedInterfaces = new List<NodeTypeInterfaceImplementation>();

                        //Vérifier que le ParentType peut avoir des enfants
                        //TODO: Optimiser
                        if (type.ParentID.HasValue)
                        {
                            var parentType =
                                typesToSave.Where(t => t.ID.Equals(type.ParentID.Value)).FirstOrDefault() ??
                                GetNodeType(type.ParentID.Value);

                            if (!parentType.IsInheritable)
                                throw new CoreException("ParentType cannot have child Types");
                        }

                        Guid typeId;
                        if (NodeTypeExists(type.Name, out typeId))
                        {
                            if (type.Status == EntityStates.New)
                                throw new CoreException("Name must be unique amongst Types and Relations");
                            if (!type.ID.Equals(typeId))
                                throw new CoreException("Cannot change TypeName. Name already exists");
                        }

                        //Tricher
                        type.Templates.ForEach(t => t.NodeTypeId = type.ID);

                        //Les lookup sont forcés à language Neutral
                        type.Properties.Where(p => p.IsLookup).ForEach(p => p.IsLanguageNeutral = true);

                        //Trouver les nouvelles Properties pour gérer les Actions
                        type.Properties.ForEach(p =>
                                                    {
                                                        if (p.IsNew)
                                                            newProperties.Add(p);
                                                        else
                                                            dirtyProperties.Add(p);
                                                    });

                        deletedProperties.AddRange(type.PropertiesInternal.Removed);

                        #region HierarchicalRules

                        #region Types

                        if (!type.IsManyToMany)
                        {
                            var originalLegitimateParentTypes = type.OriginalLegitimateParentTypes.ToList();

                            //Ajouts
                            foreach (var legitimateParentType in type.LegitimateParentTypes)
                            {
                                var typeToAdd = originalLegitimateParentTypes.Find(t => t.ID == legitimateParentType.ID);

                                if (typeToAdd == null)
                                    newHierarchicalTypeRules.Add(new HierarchicalRule
                                                                     {
                                                                         ItemID = type.ID,
                                                                         ParentItemID = legitimateParentType.ID
                                                                     });
                                else
                                    originalLegitimateParentTypes.Remove(typeToAdd);
                            }

                            //Suppressions
                            originalLegitimateParentTypes.ForEach(t => deletedHierarchicalTypeRules.Add(
                                new HierarchicalRule
                                    {
                                        ItemID = type.ID,
                                        ParentItemID = t.ID
                                    }));
                        }

                        #endregion

                        #region Nodes

                        if (!type.IsManyToMany)
                        {
                            var originalLegitimateParentNodes = type.OriginalLegitimateParentNodes.ToList();

                            //Ajouts
                            foreach (var node in type.LegitimateParentNodes)
                            {
                                var nodeToAdd = originalLegitimateParentNodes.Find(t => t.ID == node.ID);

                                if (nodeToAdd == null)
                                    newHierarchicalNodeRules.Add(new HierarchicalRule
                                                                     {
                                                                         ItemID = type.ID,
                                                                         ParentItemID = node.ID
                                                                     });
                                else
                                    originalLegitimateParentNodes.Remove(nodeToAdd);
                            }

                            //Suppressions
                            originalLegitimateParentNodes.ForEach(n => deletedHierarchicalNodeRules.Add(
                                new HierarchicalRule
                                    {
                                        ItemID = type.ID,
                                        ParentItemID = n.ID
                                    }));
                        }

                        #endregion

                        #endregion

                        #region Page Templates

                        if (!type.IsManyToMany)
                        {
                            var originalTemplates = type.OriginalTemplates.ToList();

                            //Ajouts
                            foreach (var template in type.Templates)
                            {
                                var templateToAdd = originalTemplates.Find(t => t.TemplateId == template.TemplateId
                                                                                && t.WebSiteId == template.WebSiteId);

                                if (templateToAdd == null)
                                    newTemplates.Add(template);
                                else
                                    originalTemplates.Remove(templateToAdd);
                            }

                            //Suppressions
                            deletedTemplates.AddRange(originalTemplates);
                        }

                        #endregion

                        #region Interfaces

                        newInterfaces.AddRange(type.Interfaces.Where(i => i.Status == EntityStates.New));
                        deletedInterfaces.AddRange(type.Interfaces.Removed);

                        #endregion

                        nodeTypeDataShells.Add(new NodeTypeDataShell(type,
                                                                     newProperties.Union(dirtyProperties),
                                                                     deletedProperties,
                                                                     newHierarchicalTypeRules,
                                                                     deletedHierarchicalTypeRules,
                                                                     newHierarchicalNodeRules,
                                                                     deletedHierarchicalNodeRules,
                                                                     newTemplates, deletedTemplates, newInterfaces,
                                                                     deletedInterfaces));

                        if (type.Status == EntityStates.New)
                            ActionManager.Instance.OnBeforeCreateNodeType(new ActionEventArgs<NodeType>(type));
                        else
                        {
                            newProperties.ForEach(
                                p =>
                                ActionManager.Instance.OnBeforeCreateNodeTypeProperty(
                                    new ActionEventArgs<NodeTypeProperty>(p)));
                            ActionManager.Instance.OnBeforeUpdateNodeType(new ActionEventArgs<NodeType>(type));
                            deletedProperties.ForEach(
                                p =>
                                ActionManager.Instance.OnBeforeDeleteNodeTypeProperty(
                                    new ActionEventArgs<NodeTypeProperty>(p)));
                            //Nouveau pour les dirty properties
                            //TODO: Toutes les props qui ne sont pas nouvelles sont considérées dirty...
                            dirtyProperties.ForEach(
                                p =>
                                ActionManager.Instance.OnBeforeUpdateNodeTypeProperty(
                                    new ActionEventArgs<NodeTypeProperty>(p)));
                        }
                    }
                }

                DALProviderManager.DALProvider.NodeTypeDALProvider.TransacSave(nodeTypeDataShells);
                Provider.Reload();

                foreach (var nodeTypeDataShell in nodeTypeDataShells)
                {
                    if (nodeTypeDataShell.NodeType.Status == EntityStates.Deleted)
                    {
                        ActionManager.Instance.OnAfterDeleteNodeType(
                            new ActionEventArgs<NodeType>(nodeTypeDataShell.NodeType));
                        typesToSave.RemoveAt(typesToSave.FindIndex(t =>
                                                                   t.ID.Equals(nodeTypeDataShell.NodeType.ID)));
                    }
                    else
                    {
                        var index = typesToSave.FindIndex(t =>
                                                          t.ID.Equals(nodeTypeDataShell.NodeType.ID));

                        if (nodeTypeDataShell.NodeType.Status == EntityStates.New)
                        {
                            ActionManager.Instance.OnAfterCreateNodeType(
                                new ActionEventArgs<NodeType>(nodeTypeDataShell.NodeType));
                        }
                        else
                        {
                            ActionManager.Instance.OnAfterCreateNodeTypeProperties(
                                new ActionEventArgs<IEnumerable<NodeTypeProperty>>(
                                    nodeTypeDataShell.Properties.Where(p => p.IsNew)));

                            //Nouveau pour les dirty properties
                            //TODO: Toutes les props qui ne sont pas nouvelles sont considérées dirty...
                            nodeTypeDataShell.Properties.Where(p => !p.IsNew).ForEach(
                                p =>
                                ActionManager.Instance.OnAfterUpdateNodeTypeProperty(
                                    new ActionEventArgs<NodeTypeProperty>(p)));

                            ActionManager.Instance.OnAfterDeleteNodeTypeProperties(
                                new ActionEventArgs<IEnumerable<NodeTypeProperty>>(
                                    typesToSave[index].PropertiesInternal.Removed.AsEnumerable()));

                            #region Interfaces

                            ActionManager.Instance.OnAfterImplementNodeTypeInterfaces(new
                                                                                          ActionEventArgs
                                                                                          <
                                                                                          IEnumerable
                                                                                          <
                                                                                          NodeTypeInterfaceImplementation
                                                                                          >>(typesToSave[index]
                                                                                                 .Interfaces.Where(
                                                                                                     i =>
                                                                                                     i.Status ==
                                                                                                     EntityStates.New)));

                            ActionManager.Instance.OnAfterUnimplementNodeTypeInterfaces(new
                                                                                            ActionEventArgs
                                                                                            <
                                                                                            IEnumerable
                                                                                            <
                                                                                            NodeTypeInterfaceImplementation
                                                                                            >>(typesToSave[index]
                                                                                                   .Interfaces.Removed));

                            #endregion

                            //Doit venir à la toute fin !
                            ActionManager.Instance.OnAfterUpdateNodeType(
                                new ActionEventArgs<NodeType>(nodeTypeDataShell.NodeType));
                        }

                        //On fait pas la même chose pour les 
                        //LegitimateParentNodes ??? (logique copier des anciennes méthodes)
                        typesToSave[index].OriginalLegitimateParentTypes = typesToSave[index].LegitimateParentTypes;

                        if (typesToSave[index].Status == EntityStates.New)
                            typesToSave[index].Status = EntityStates.Loaded;
                    }
                }
            }

            //La collection n'est plus valide je crois après avoir fait un Provider.Reload()
            //Exemple il ne devrait plus y avoir de typesToSave[index].PropertiesInternal.Removed
            //TODO : Reconstruire la collection avant de la retourner
            return typesToSave;
        }

        public static void DeleteNodeType(Guid pNodeTypeID)
        {
            DeleteNodeType(GetNodeType(pNodeTypeID));
        }

        public static void DeleteNodeType(string pNodeTypeName)
        {
            DeleteNodeType(GetNodeType(pNodeTypeName));
        }

        public static void DeleteNodeType(NodeType type)
        {
            type.Status = EntityStates.Deleted;
            SaveType(type);
        }

        public static void DeleteNodeTypeInterface(Guid interfaceId)
        {
            DeleteNodeTypeInterface(GetNodeTypeInterface(interfaceId));
        }

        public static void DeleteNodeTypeInterface(NodeTypeInterface pInterface)
        {
            pInterface.Status = EntityStates.Deleted;
            SaveInterface(pInterface);
        }

        /// <summary>
        ///   Vérifie l'existence d'un Type Medianamik avec le TypeName passé en paramètre.
        /// </summary>
        /// <param name = "pTypeName">Le TypeName à vérifier</param>
        /// <returns>True si le Type existe, sinon False</returns>
        public static bool NodeTypeExists(string pTypeName, out Guid pTypeId)
        {
            if (string.IsNullOrEmpty(pTypeName))
                throw new ArgumentException("Parameter pTypeName cannot have the value 'null' or string.Empty");

            var nodeType = GetNodeType(pTypeName);
            var exists = (nodeType != null);
                //DALProviderManager.DALProvider.NodeTypeDALProvider.NodeTypeExists(pTypeName, out typeId);
            pTypeId = exists ? nodeType.ID : Guid.Empty;
            return exists;
        }

        public static bool NodeTypeExists(string pTypeName)
        {
            Guid fake;
            return NodeTypeExists(pTypeName, out fake);
        }

        public static bool NodeTypeInterfaceExists(string interfaceName, out Guid interfaceId)
        {
            if (string.IsNullOrEmpty(interfaceName))
                throw new ArgumentException("Parameter interfaceName cannot be null or have the value String.Empty");

            var nodeTypeInterface = GetNodeTypeInterface(interfaceName);
            var exists = (nodeTypeInterface != null);
            interfaceId = exists ? nodeTypeInterface.ID : Guid.Empty;
            return exists;
        }

        public static bool NodeTypeInterfaceExists(string interfaceName)
        {
            Guid fake;
            return NodeTypeExists(interfaceName, out fake);
        }

        public static NodeTypeProperty GetTypeProperty(Guid pTypePropertyID)
        {
            var property = (from prop in Provider.AllProperties
                            where prop.ID.Equals(pTypePropertyID)
                            select prop).FirstOrDefault();
            return property;
        }

        public static void DeleteTypeProperty(Guid pTypePropertyID)
        {
            var p = GetTypeProperty(pTypePropertyID);

            ActionManager.Instance.OnBeforeDeleteNodeTypeProperty(new ActionEventArgs<NodeTypeProperty>(p));
            DALProviderManager.DALProvider.NodeTypeDALProvider.DeleteTypeProperty(pTypePropertyID);

            Provider.Reload();
            ActionManager.Instance.OnAfterDeleteNodeTypeProperties(
                new ActionEventArgs<IEnumerable<NodeTypeProperty>>(new List<NodeTypeProperty> {p}));
        }

        public static void UpdateProperty(NodeTypeProperty pTypeProperty)
        {
            if (pTypeProperty.IsNew)
            {
                throw new CoreException("Cannot update a new NodeTypeProperty.");
            }

            //On force languageneutral pour les lookup
            if (pTypeProperty.IsLookup)
                pTypeProperty.IsLanguageNeutral = true;

            ActionManager.Instance.OnBeforeUpdateNodeTypeProperty(new ActionEventArgs<NodeTypeProperty>(pTypeProperty));
            DALProviderManager.DALProvider.NodeTypeDALProvider.UpdateTypeProperty(pTypeProperty);

            Provider.Reload();
            ActionManager.Instance.OnAfterUpdateNodeTypeProperty(new ActionEventArgs<NodeTypeProperty>(pTypeProperty));
        }

        public static List<String> GetPropertyGroupNames()
        {
            var groupNames = (from prop in Provider.AllProperties
                              where !string.IsNullOrEmpty(prop.GroupName)
                              select prop.GroupName).Distinct();

            return groupNames.ToList();
        }

        internal static IEnumerable<NodeTypeProperty> GetNodeTypeProperties(Guid pNodeTypeId)
        {
            var nodeTypeIds = new HashSet<Guid>(GetParentTypes(pNodeTypeId, true).Select(t => t.ID));

            var properties = from prop in Provider.AllProperties
                             where nodeTypeIds.Contains(prop.TypeID)
                             select prop;

            return properties;
        }

        internal static IEnumerable<NodeTypeInterfaceProperty> GetNodeTypeInterfaceProperties(Guid interfaceId)
        {
            return Provider.AllInterfaceProperties.Where(i => i.Owner.ID.Equals(interfaceId));
        }

        internal static Collection<OneToManyRelation> GetPrimaryRelations(IEnumerable<Guid> pTypeIds)
        {
            return DALProviderManager.DALProvider.NodeTypeDALProvider.GetPrimaryRelations(pTypeIds);
        }

        internal static Collection<OneToManyRelation> GetForeignRelations(IEnumerable<Guid> pTypeIds)
        {
            return DALProviderManager.DALProvider.NodeTypeDALProvider.GetForeignRelations(pTypeIds);
        }

        internal static Collection<ManyToManyRelation> GetManyToManyRelations(IEnumerable<Guid> pTypeIds)
        {
            return DALProviderManager.DALProvider.NodeTypeDALProvider.GetManyToManyRelations(pTypeIds);
        }

        private static NodeType InsertType(NodeType pType)
        {
            if (pType.Properties.Any(p => !p.IsNew))
            {
                throw new CoreException("New Type must have new Properties");
            }

            return SaveType(pType);
        }

        private static NodeType UpdateType(NodeType pType)
        {
            if (pType.Status == EntityStates.New)
            {
                throw new ArgumentException("pType cannot have Status == EntityStates.New");
            }

            return SaveType(pType);
        }

        public static NodeTypeInterfaceProperty DeleteNodeTypeInterfaceProperty(Guid interfacePropertyId)
        {
            return Provider.AllInterfaceProperties
                .FirstOrDefault(prop => prop.ID.Equals(interfacePropertyId));
        }
    }
}